package org.proteored.miapeapi.xml.pride.util;

import java.util.ArrayList;
import java.util.List;

import org.proteored.miapeapi.cv.Accession;
import org.proteored.miapeapi.cv.ControlVocabularyManager;
import org.proteored.miapeapi.cv.ControlVocabularySet;
import org.proteored.miapeapi.cv.ControlVocabularyTerm;
import org.proteored.miapeapi.interfaces.MatchMode;
import org.proteored.miapeapi.xml.mzml.util.MzMLControlVocabularyXmlFactory;
import org.proteored.miapeapi.xml.pride.autogenerated.CvParamType;
import org.proteored.miapeapi.xml.pride.autogenerated.ObjectFactory;
import org.proteored.miapeapi.xml.pride.autogenerated.ParamType;
import org.proteored.miapeapi.xml.pride.autogenerated.UserParamType;
import org.proteored.miapeapi.xml.util.MiapeXmlUtil;

import uk.ac.ebi.jmzml.model.mzml.CVParam;
import uk.ac.ebi.jmzml.model.mzml.ParamGroup;
import uk.ac.ebi.jmzml.model.mzml.ReferenceableParamGroup;
import uk.ac.ebi.jmzml.model.mzml.ReferenceableParamGroupList;
import uk.ac.ebi.jmzml.model.mzml.ReferenceableParamGroupRef;
import uk.ac.ebi.jmzml.model.mzml.UserParam;

public class PrideControlVocabularyXmlFactory {
	private static org.apache.log4j.Logger log = org.apache.log4j.Logger
			.getLogger("log4j.logger.org.proteored");

	public static final String URI = "URI";
	public static final String PARAMETER_FILE = "parameter file";
	public static final String PARAMETERS = "Parameters";
	public static final String MODEL = "Model";
	public static final String CATALOG_NUMBER = "Catalog number";
	public static final String MANUFACTURER = "Manufacturer";
	public static final String DESCRIPTION = "Description";
	public static final String CUSTOMIZATIONS = "Customizations";
	public static final String COMMENTS = "Comments";
	public static final String VERSION = "Version";

	public static final String MZDATA_VERSION = "1.05";
	public static final String PRIDE_VERSION = "2.1";

	public static final String ION_OPTICS = "Ion optics";
	public static final String SCORE = "Score";
	public static final String CHARGE_STATE = "Charge State";
	public static final String ACTIVATION_DISSOCIATION_COMPONENT = "Hardware where activation and/or dissociation occurs";

	public static final String TERM_SEPARATOR = "\n";
	public static final String REPLACEMENT_RESIDUE = "Residue replacement";

	public static final String GAS_PRESSURE_UNIT = "Gas pressure units";

	public static final String VALIDATION_STATUS_TEXT = "Validation status";

	private final ObjectFactory factory;
	private final ControlVocabularyManager controlVocabularyUtil;

	public PrideControlVocabularyXmlFactory(ObjectFactory factory,
			ControlVocabularyManager controlVocabularyUtil) {
		this.factory = factory;
		this.controlVocabularyUtil = controlVocabularyUtil;
	}

	public ControlVocabularyManager getCvManager() {
		return this.controlVocabularyUtil;
	}

	/**
	 * Create a ParamType with either: - a FuGECommonOntologyCvParamType or - a
	 * FuGECommonOntologyUserParamType
	 * 
	 * @param name
	 * @param cvSets
	 * @return a ParamType
	 */
	public ParamType createParamType(String name, String value,
			ControlVocabularySet... cvSets) {
		if (name == null)
			return null;
		ParamType param;
		if (cvSets == null || cvSets.length == 0) {
			param = factory.createParamType();
			UserParamType userParam = createUserParam(name, value);
			param.getCvParamOrUserParam().add(userParam);
			return param;
		}
		for (ControlVocabularySet cvSet : cvSets) {
			if (controlVocabularyUtil.isCV(name, cvSet)) {
				param = factory.createParamType();
				CvParamType cvParam = createCvParam(name, value, cvSet);
				param.getCvParamOrUserParam().add(cvParam);
				return param;
			}
		}
		param = factory.createParamType();
		UserParamType userParam = createUserParam(name, value);
		param.getCvParamOrUserParam().add(userParam);
		return param;

	}

	/**
	 * Create a CvParamType or UserParamType
	 * 
	 * @param name
	 * @param value
	 * @param cvSet
	 * @return the CvParamType UserParamType
	 */
	public Object createCvParamOrUserParam(String name, String value,
			ControlVocabularySet... cvSet) {
		if (name != null && !name.equals("")) {
			// if is cvParam

			CvParamType cvParam = createCvParam(name, value, cvSet);
			if (cvParam != null)
				return cvParam;

			// if it is null is because it is not a CV, so it is a userParam
			UserParamType userParam = createUserParam(name, value);
			return userParam;
		}
		return null;
	}

	/**
	 * Adds a cvParam or a userParam to a ParamType checking if it is a CV in a
	 * MIAPE section
	 * 
	 * @param paramType
	 * @param name
	 * @param value
	 * @param cvSets
	 * @return a ParamType
	 */
	public ParamType addCvParamOrUserParamToParamType(ParamType paramType,
			String name, String value, ControlVocabularySet... cvSets) {
		final Object cvParamOrUserParam = createCvParamOrUserParam(name, value,
				cvSets);
		if (cvParamOrUserParam != null) {
			if (paramType == null)
				paramType = factory.createParamType();
			paramType.getCvParamOrUserParam().add(cvParamOrUserParam);
			return paramType;
		}
		return null;
	}

	/**
	 * Adds a cvParam to a ParamType
	 * 
	 * @param paramType
	 * @param cvId
	 * @param name
	 * @param value
	 * @param cvRef
	 */
	public ParamType addCvParamToParamType(ParamType paramType, Accession cvId,
			String name, String value, String cvRef) {
		final CvParamType cvParam = createCvParam(cvId, name, value, cvRef);
		if (cvParam != null) {
			if (paramType == null)
				paramType = factory.createParamType();
			paramType.getCvParamOrUserParam().add(cvParam);
			return paramType;
		}
		return null;
	}

	/**
	 * Adds a cvParam or a userParam to a ParamType checking if it is a CV in a
	 * MIAPE section
	 * 
	 * @param paramType
	 * @param name
	 * @param value
	 * @return a ParamType
	 */
	public ParamType addUserParamToParamType(ParamType paramType, String name,
			String value) {
		final UserParamType userParam = createUserParam(name, value);
		if (userParam != null) {
			if (paramType == null)
				paramType = factory.createParamType();
			paramType.getCvParamOrUserParam().add(userParam);
			return paramType;
		}
		return null;
	}

	/**
	 * Create a UserParamType
	 * 
	 * @param name
	 * @return the UserParamType
	 */
	public CvParamType createCvParam(String name, String value,
			ControlVocabularySet... cvSets) {
		if (cvSets != null && name != null && !name.equals("")) {
			for (ControlVocabularySet cvSet : cvSets) {
				final ControlVocabularyTerm cvTermByPreferredName = cvSet
						.getCVTermByPreferredName(name);
				if (cvTermByPreferredName != null) {
					String cvRef = cvTermByPreferredName.getCVRef();
					CvParamType cvParam = factory.createCvParamType();
					if (value != null)
						cvParam.setValue(value);
					cvParam.setAccession(cvTermByPreferredName
							.getTermAccession().toString());
					cvParam.setName(name);
					cvParam.setCvLabel(cvRef);
					return cvParam;
				}
			}
		}
		return null;
	}

	// public CvParamType createCvParam(String name, String value,
	// ControlVocabularySet... cvSets) {
	// if (cvSets != null && name != null && !name.equals("")) {
	// for (ControlVocabularySet cvSet : cvSets) {
	// Accession cvId = controlVocabularyUtil.getControlVocabularyId(name,
	// cvSet);
	//
	// if (cvId != null) {
	// String cvRef = controlVocabularyUtil.getCVRef(cvId, cvSet);
	// CvParamType cvParam = factory.createCvParamType();
	// if (value != null)
	// cvParam.setValue(value);
	// cvParam.setAccession(cvId.toString());
	// cvParam.setName(name);
	// cvParam.setCvLabel(cvRef);
	// return cvParam;
	// }
	// }
	// }
	// return null;
	// }

	/**
	 * Create a {@link CvParamType} giving all the information in the parameters
	 * 
	 * @param cvId
	 * @param name
	 * @param value
	 * @param cvRef
	 * @return the {@link CvParamType}
	 */
	public CvParamType createCvParam(Accession cvId, String name, String value,
			String cvRef) {
		if (name == null)
			return null;

		CvParamType cvParam = factory.createCvParamType();

		if (value != null)
			cvParam.setValue(value);
		cvParam.setAccession(cvId.toString());
		cvParam.setName(name);
		cvParam.setCvLabel(cvRef);

		return cvParam;
	}

	/**
	 * Create a UserParamType
	 * 
	 * @param name
	 * @return the UserParamType
	 */
	public UserParamType createUserParam(String name, String value) {
		UserParamType userParamType = factory.createUserParamType();
		if (value != null)
			userParamType.setValue(value);
		userParamType.setName(name);
		return userParamType;
	}

	public static String writeParam(CvParamType param) {
		StringBuilder sb = new StringBuilder();
		if (param == null)
			return null;
		String value = param.getValue();
		String name = param.getName();

		if (value == null || value.equals("")) {
			sb.append(name);
		} else {
			sb.append(name);
			sb.append("=");
			sb.append(value);
		}
		return sb.toString();
	}

	public static String writeParam(UserParamType param) {
		StringBuilder sb = new StringBuilder();
		if (param == null)
			return null;
		String value = param.getValue();
		String name = param.getName();
		if (value == null || value.equals("")) {
			sb.append(name);
		} else {
			sb.append(name);
			sb.append("=");
			sb.append(value);
		}
		return sb.toString();
	}

	public static String writeParam(ParamType paramType) {
		if (paramType == null)
			return null;
		StringBuilder sb = new StringBuilder();
		for (Object cvOrUserParam : paramType.getCvParamOrUserParam()) {
			if (!sb.toString().equals(""))
				sb.append(MiapeXmlUtil.TERM_SEPARATOR);
			if (cvOrUserParam instanceof CvParamType) {
				CvParamType param = (CvParamType) cvOrUserParam;

				String value = param.getValue();
				String name = param.getName();
				if (value == null || value.equals("")) {
					sb.append(name);
				} else {
					sb.append(name);
					sb.append("=");
					sb.append(value);
				}
			} else if (cvOrUserParam instanceof UserParamType) {
				UserParamType param = (UserParamType) cvOrUserParam;

				String value = param.getValue();
				String name = param.getName();
				if (value == null || value.equals("")) {
					sb.append(name);
				} else {
					sb.append(name);
					sb.append("=");
					sb.append(value);
				}
			}
		}
		if (!sb.toString().trim().equals(""))
			return sb.toString();
		return null;
	}

	/**
	 * Search a cvParam in a list of params of a {@link ParamType}
	 * 
	 * @param paramType
	 * @param accession
	 * @return the value of the cvParam
	 */
	public static CvParamType getCvFromParamType(ParamType paramType,
			Accession accession) {
		if (paramType != null) {
			for (Object param : paramType.getCvParamOrUserParam()) {
				if (param instanceof CvParamType) {
					CvParamType cvParam = (CvParamType) param;
					if (accession.equals(cvParam.getAccession())) {
						return cvParam;
					}
				}
			}
		}
		return null;
	}

	/**
	 * Search any of the cvParams from an enum in a list of params of a
	 * {@link ParamType}
	 * 
	 * @param paramType
	 * @param cvSets
	 * @return the value of the cvParam
	 */
	public static CvParamType getCvFromParamType(ParamType paramType,
			ControlVocabularySet... cvSets) {
		if (paramType != null && cvSets != null) {
			for (ControlVocabularySet cvSet : cvSets) {
				List<ControlVocabularyTerm> possibleValues = cvSet
						.getPossibleValues();
				if (possibleValues != null) {
					for (Object param : paramType.getCvParamOrUserParam()) {
						if (param instanceof CvParamType) {
							CvParamType cvParam = (CvParamType) param;
							for (ControlVocabularyTerm controlVocabularyField : possibleValues) {
								if (controlVocabularyField.getTermAccession()
										.equals(cvParam.getAccession())) {
									return cvParam;
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Search all cvParams from an enum in a list of params of a
	 * {@link ParamType}
	 * 
	 * @param paramType
	 * @param cvSets
	 * @return all cvParam that belongs to the cvSets
	 */
	public static List<CvParamType> getCvsFromParamType(ParamType paramType,
			ControlVocabularySet... cvSets) {

		if (paramType != null && cvSets != null) {
			List<CvParamType> ret = new ArrayList<CvParamType>();
			for (ControlVocabularySet cvSet : cvSets) {
				List<ControlVocabularyTerm> possibleValues = cvSet
						.getPossibleValues();
				if (possibleValues != null) {
					for (Object param : paramType.getCvParamOrUserParam()) {
						if (param instanceof CvParamType) {
							CvParamType cvParam = (CvParamType) param;
							for (ControlVocabularyTerm controlVocabularyField : possibleValues) {
								if (controlVocabularyField.getTermAccession()
										.equals(cvParam.getAccession())) {
									ret.add(cvParam);
								}
							}
						}
					}
				}
			}
			if (!ret.isEmpty())
				return ret;
		}

		return null;
	}

	public static boolean findTextInUserParamType(UserParamType userParam,
			String[] textList, String matchMode) {
		for (String text : textList) {
			if (matchMode.equals(MatchMode.ANYWHERE)) {
				if (userParam.getName().toLowerCase()
						.contains(text.toLowerCase())) {
					return true;
				}
			} else {
				if (userParam.getName().toLowerCase()
						.equals(text.toLowerCase())) {
					return true;
				}
			}
		}
		return false;
	}

	public ParamType getParamTypeFromMZMLParamGroup(ParamGroup activation,
			ReferenceableParamGroupList referenceableParamGroupList) {
		if (activation == null)
			return null;
		ParamType ret = this.factory.createParamType();
		if (activation.getCvParam() != null) {
			for (CVParam mzMLcvParam : activation.getCvParam()) {
				CvParamType prideCvParam = getCVParam(mzMLcvParam);
				if (prideCvParam != null)
					ret.getCvParamOrUserParam().add(prideCvParam);
			}
		}
		if (activation.getUserParam() != null) {
			for (UserParam mzMLuserParam : activation.getUserParam()) {
				UserParamType prideUserParam = getUserParam(mzMLuserParam);
				if (prideUserParam != null)
					ret.getCvParamOrUserParam().add(prideUserParam);
			}
		}
		// TODO mirar el Paramgroupref
		if (activation.getReferenceableParamGroupRef() != null) {
			for (ReferenceableParamGroupRef paramGroupRef : activation
					.getReferenceableParamGroupRef()) {
				final ReferenceableParamGroup referencedParamGroup = MzMLControlVocabularyXmlFactory
						.searchParamGroupInReferenceableParamGroupList(
								paramGroupRef.getRef(),
								referenceableParamGroupList);
				if (referencedParamGroup != null) {
					if (referencedParamGroup.getCvParam() != null) {
						for (CVParam mzMLCVParam : referencedParamGroup
								.getCvParam()) {
							CvParamType prideCvParam = getCVParam(mzMLCVParam);
							if (prideCvParam != null)
								ret.getCvParamOrUserParam().add(prideCvParam);
						}
					}
					if (referencedParamGroup.getUserParam() != null) {
						for (UserParam mzMLUserParam : referencedParamGroup
								.getUserParam()) {
							UserParamType prideUserParam = getUserParam(mzMLUserParam);
							if (prideUserParam != null)
								ret.getCvParamOrUserParam().add(prideUserParam);
						}
					}
				}
			}
		}
		return ret;
	}

	private UserParamType getUserParam(UserParam mzMLuserParam) {
		if (mzMLuserParam == null)
			return null;
		UserParamType ret = this.factory.createUserParamType();
		ret.setName(mzMLuserParam.getName());
		ret.setValue(mzMLuserParam.getValue());
		return ret;
	}

	private CvParamType getCVParam(CVParam mzMLcvParam) {
		if (mzMLcvParam == null)
			return null;
		CvParamType ret = this.factory.createCvParamType();
		ret.setAccession(mzMLcvParam.getAccession());
		ret.setCvLabel(mzMLcvParam.getCvRef());
		ret.setName(mzMLcvParam.getName());
		ret.setValue(mzMLcvParam.getValue());
		return ret;
	}

	/**
	 * This function parse the parameter string and look for NEW LINE
	 * separations. In case of find them, returns a {@link CvParamType} or a
	 * {@link UserParamType} for each line.<br>
	 * For each line, also looks for a EQUAL "=" character, and if find it,
	 * separate the values in a name/value pair.<br>
	 * For each name/value pair, look for a {@link CvParamType} in the
	 * {@link ControlVocabularySet} passed on "cvSets" param.<br>
	 * If not NEWLINES are found or if not EQUAL "=" character is found, the
	 * function will return a {@link CvParamType} adding the string as a value
	 * of the individualCVTerm passed as parameter.<br>
	 * 
	 * @param string
	 * @param cvManager
	 * @param individualCVTerm
	 * @param cvSets
	 * @return
	 */
	public List<Object> getParamTypesFromString(String string,
			ControlVocabularyManager cvManager,
			ControlVocabularyTerm individualCVTerm,
			ControlVocabularySet... cvSets) {
		List<Object> ret = new ArrayList<Object>();
		if (string != null && !"".equals(string)) {
			// Check if the string is divided by NEW LINE
			// character
			String newLine = System.getProperty("line.separator");
			String[] lines = null;
			if (string.contains(newLine)) {
				lines = string.split(newLine);
				log.info("string separated on " + lines.length + " lines");
			} else {
				newLine = "\n";
				if (string.contains(newLine)) {
					lines = string.split(newLine);
					log.info("string separated on " + lines.length + " lines");
				} else {
					lines = new String[1];
					lines[0] = string;
					log.info("string taken as a single string: " + string);
				}
			}

			for (String line : lines) {
				boolean alreadytaken = false;
				// check if it is divided by an equal
				String equal = "=";
				if (line.contains(equal)) {
					String[] nameValuePair = line.split(equal);
					if (nameValuePair.length == 2 || nameValuePair.length == 1) {

						alreadytaken = true;
						String name = nameValuePair[0];
						String value = null;

						if (nameValuePair.length == 2)
							value = nameValuePair[1];
						log.info("string taken as name value pair: name="
								+ name + " value=" + value);
						Object createCvParamOrUserParam = this
								.createCvParamOrUserParam(name, value, cvSets);
						ret.add(createCvParamOrUserParam);
					}
				}
				if (!alreadytaken) {
					if (individualCVTerm != null) {
						log.info("string taken as CV: " + line);
						CvParamType cvParam = this.createCvParam(
								individualCVTerm.getTermAccession(),
								individualCVTerm.getPreferredName(), line,
								individualCVTerm.getCVRef());
						if (cvParam != null)
							ret.add(cvParam);
					} else {
						log.info("string taken as one line: " + line);
						Object createCvParamOrUserParam = this
								.createCvParamOrUserParam(line, null, cvSets);
						ret.add(createCvParamOrUserParam);
					}

				}
			}

		}
		return ret;
	}
}
